﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.proxy;
using snat.proxy.items;
using snat.proxy.sqlapi;
using System.Threading.Tasks;
using snat.model.EventFramework;
using snat.model.events;
using snat.model.Monitors.MessageCount;
//using snat.model.EventFramework;

namespace snat.model
{
    public delegate void systemHandler();
    public delegate void messageDelegate(Message m);
    public delegate void nodeDelegate(BaseNode b);
    public delegate void edgeDelegate(Edge e);

    public class NetworkModel
    {
        public event systemHandler TapeLoaded;
        public event systemHandler TapeStarted;
        public event systemHandler TapePaused;
        public event systemHandler TapeRunning;
        public event systemHandler TapeEnded;



        public event messageDelegate NewMessage;
        private void messageSent(Message m) {
            if (NewMessage != null) NewMessage(m);
        }


        public event EventHandler<NodeArgs> NewNode;
        public event nodeDelegate NewNewNode;
        private void nodeAdded(BaseNode b) {
            if (NewNode != null) NewNode(this, new NodeArgs(b));
            if (NewNewNode != null) NewNewNode(b);
        }


        public event EventHandler<EdgeArgs> NewEdge;
        public event edgeDelegate NewNewEdge;
        public void edgeAdded(Edge e) {
            if (NewEdge != null) NewEdge(this, new EdgeArgs(e));
            if (NewNewEdge != null) NewNewEdge(e);
        }

        /* Structure Network Model Properties */
        public int NodeCount {
            get {
                return Nodes.Count;
            }
        }


        public int EdgeCount {
            get {
                return Edges.Count;
            }
        }

        /*** MOVE THIS TO STRUCTURE ALGORITHMS PACKAGE ***/
        private static NetworkModel instance;
        public static NetworkModel Instance {
            get {
                if (instance == null) {
                    instance = new NetworkModel(new BaseNodeFactory());
                }
                return instance;
            }
        }

        /// <summary>
        /// nodeID -> BaseNode
        /// </summary>
        private Dictionary<int, BaseNode> nodes;
        public Dictionary<int, BaseNode> Nodes {
            get {
                return nodes;
            }
        }
        private List<Edge> edges;
        public List<Edge> Edges {
            get {
                return edges;
            }
        }
        //ListProxy<Message> allMessages;
        private DateTime startTime;
        public DateTime StartTime {
            get {
                return startTime;
            }
        }

        private DateTime endTime;
        public DateTime EndTime {
            get {
                return endTime;
            }
        }
        private DateTime currentTime;
        public DateTime CurrentTime {
            get {
                return currentTime;
            }
        }

        private IEnumerator<Message> messageIterator;

        //TODO TODO MOVE TO INIT
        private readonly NodeFactory nodefactory;
        private NetworkModel(NodeFactory _nodefactory) {
            nodes = new Dictionary<int, BaseNode>();
            edges = new List<Edge>();
            nodefactory = _nodefactory;

        }

        private bool paused = true;
        public bool Paused {
            get {
                return paused;
            }
        }

        public void Play() {
            paused = false;
            if (TapeRunning != null) TapeRunning();
            while (!Paused) {
                Step(1);
            }
        }

        public void Init() {
            DataProxy.Instance.connect("siroberts.co.uk", 3306, "sirobert_snat", "sirobert_snag", "mOOch1ng");
            //DataProxy.Instance.connect("ptay89.co.uk", 3306, "ptay89c_snat", "ptay89c_snag", "mOOch1ng");


            messageIterator = MessageQuery.All().GetEnumerator();
            messageIterator.MoveNext();
            startTime = messageIterator.Current.TimeStamp;
            currentTime = messageIterator.Current.TimeStamp;
            endTime = MessageQuery.LastMessage().TimeStamp;
            Console.WriteLine("Initializing the nodes...");

            ListProxy<Entity> allEntities = EntityQuery.All();
            if (allEntities.Count <= 0) {
                Console.WriteLine("No entities were discovered");
                if (TapeEnded != null) TapeEnded();
                paused = true;
                return;
            }
            foreach (Entity e in allEntities) {
                BaseNode newNode = nodefactory.Instantiate(e);
                if (e != null) {
                    nodes.Add(e.ID, newNode);
                    nodeAdded(newNode);
                }
            }


            Console.WriteLine("Initializing the edges...");



            ListProxy<Connection> initialConnections = ConnectionQuery.Init();
            if (initialConnections.Count > 0) {
                foreach (Connection c in initialConnections) {
                    Edge newEdge = new Edge(c, DateTime.MinValue);
                    //ensure the two nodes were added by the factory and are therefore important
                    if (nodes.ContainsKey(c.From.ID) && nodes.ContainsKey(c.To.ID)) {
                        edges.Add(newEdge);
                        edgeAdded(newEdge);
                    }
                }
            }
        }

        public void Pause() {
            if (TapePaused != null) TapePaused();
            paused = true;
        }

        void NetworkStructure(out Edge edges, out List<BaseNode> nodes) {
            throw new NotImplementedException("This method needed if saving the graph state is ever necessary");
        }

        public void triggerSend(BaseNode sender, Message m, List<BaseNode> recipients) {
            if (sender != null) {
                sender.triggerSend(m, recipients);
                foreach (BaseNode recipient in recipients) {
                    AddEdge(sender, recipient, m.TimeStamp);
                }
            }
            else Console.WriteLine("Send not registered");
            Parallel.ForEach(recipients, recipient => {
                if (recipient != null) {
                    recipient.triggerRecieve(m, sender);
                }
                else Console.WriteLine("Recieve not registered");
            });
        }

        public void Step(int steps) {
            while (steps-- > 0) {
                Message current = messageIterator.Current;

                currentTime = current.TimeStamp;
                BaseNode from = nodes[current.Sender.ID];
                //trigger event for from.send
                List<BaseNode> to = new List<BaseNode>();
                foreach (Entity e in current.Recipients) {
                    BaseNode recipient = nodes[e.ID]; //don't bother newing it
                    if (recipient != null) to.Add(recipient);
                }
                triggerSend(from, current, to);

                Console.WriteLine(current.ToString());
                messageSent(current);


                if (!messageIterator.MoveNext()) {
                    if (TapeEnded != null) TapeEnded();
                    paused = true;
                    return;
                }
            }
        }

        public void Step(DateTime time) {
            while (currentTime < time) {
                Step(1);
            }
        }

        internal void AddEdge(BaseNode sender, BaseNode recipient, DateTime timestamp) {
            Edge toAdd = new Edge(sender, recipient, timestamp);
            //if (!edges.Contains(toAdd)) {
            //    edges.Add(toAdd);
            //    edgeAdded(toAdd);
            //}


            //TODO TODO TODO TODO ONLY RE-ADD ON NEW EDGE!! TODO!!

            edges.Add(toAdd);
            edgeAdded(toAdd);
        }
    }
}
